// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.adwords.axis;

import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroup;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupAd;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupAdOperation;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupAdReturnValue;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupAdServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupCriterionReturnValue;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupCriterionServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupOperation;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupReturnValue;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.AdGroupStatus;
import com.google.api.ads.adwords.axis.v201209.cm.Address;
import com.google.api.ads.adwords.axis.v201209.cm.ApiException;
import com.google.api.ads.adwords.axis.v201209.cm.Bid;
import com.google.api.ads.adwords.axis.v201209.cm.BiddableAdGroupCriterion;
import com.google.api.ads.adwords.axis.v201209.cm.Budget;
import com.google.api.ads.adwords.axis.v201209.cm.BudgetBudgetDeliveryMethod;
import com.google.api.ads.adwords.axis.v201209.cm.BudgetBudgetPeriod;
import com.google.api.ads.adwords.axis.v201209.cm.BudgetOperation;
import com.google.api.ads.adwords.axis.v201209.cm.BudgetServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.Campaign;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignAdExtension;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignAdExtensionOperation;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignAdExtensionReturnValue;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignAdExtensionServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignOperation;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignReturnValue;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.CampaignStatus;
import com.google.api.ads.adwords.axis.v201209.cm.ExperimentPage;
import com.google.api.ads.adwords.axis.v201209.cm.ExperimentServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.GeoLocation;
import com.google.api.ads.adwords.axis.v201209.cm.GeoLocationSelector;
import com.google.api.ads.adwords.axis.v201209.cm.GeoLocationServiceInterface;
import com.google.api.ads.adwords.axis.v201209.cm.Keyword;
import com.google.api.ads.adwords.axis.v201209.cm.KeywordMatchSetting;
import com.google.api.ads.adwords.axis.v201209.cm.KeywordMatchType;
import com.google.api.ads.adwords.axis.v201209.cm.LocationExtension;
import com.google.api.ads.adwords.axis.v201209.cm.LocationExtensionSource;
import com.google.api.ads.adwords.axis.v201209.cm.ManualCPC;
import com.google.api.ads.adwords.axis.v201209.cm.ManualCPCAdGroupBids;
import com.google.api.ads.adwords.axis.v201209.cm.Money;
import com.google.api.ads.adwords.axis.v201209.cm.Operator;
import com.google.api.ads.adwords.axis.v201209.cm.Predicate;
import com.google.api.ads.adwords.axis.v201209.cm.PredicateOperator;
import com.google.api.ads.adwords.axis.v201209.cm.Selector;
import com.google.api.ads.adwords.axis.v201209.cm.Setting;
import com.google.api.ads.adwords.axis.v201209.cm.TextAd;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.common.lib.auth.ClientLoginTokens;
import com.google.api.ads.common.lib.conf.ConfigurationHelper;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.googleapis.auth.clientlogin.ClientLoginResponseException;

import org.apache.commons.configuration.Configuration;

import java.io.IOException;
import java.rmi.RemoteException;

/**
 * Helper class for AdWords Axis examples. Provides functionality to lazily
 * create prerequisite objects for testing.
 *
 * @author Kevin Winter
 */
public class AdWordsAxisTestHelper {

  private String authToken;
  private Long testCampaignId;
  private Long testAdGroupId;
  private Long testTextAdId;
  private Long testKeywordId;
  private Long testLocationExtensionId;
  private Configuration config;
  protected AdWordsServices adWordsServices;

  /**
   * Instantiates this helper class and obtains an AuthToken (which is cached).
   */
  public AdWordsAxisTestHelper() {
    try {
      this.authToken =
          new ClientLoginTokens.Builder().forApi(ClientLoginTokens.Api.ADWORDS).fromFile().build()
              .requestToken();
      this.adWordsServices = new AdWordsServices();
    } catch (ValidationException e) {
      throw new IllegalStateException("Could not instantiate AdWordsServices.", e);
    } catch (ClientLoginResponseException e) {
      throw new IllegalStateException("Could not obtain an AuthToken.", e);
    } catch (IOException e) {
      throw new IllegalStateException("Could not obtain an AuthToken.", e);
    } catch (ConfigurationLoadException e) {
      throw new IllegalStateException("Could not obtain an AuthToken.", e);
    }
  }

  /**
   * @return the configuration used to create the {@link AdWordsSession}.
   */
  public Configuration getConfig() {
    if (config == null) {
      try {
        config = new ConfigurationHelper().fromFile("ads.properties");
      } catch (ConfigurationLoadException e) {
        throw new IllegalStateException("Could not obtain an AuthToken.", e);
      }
    }
    return config;
  }

  /**
   * Gets a brand-new AdWordsSession (but with re-used authToken).
   */
  public AdWordsSession getSession() {
    try {
      return new AdWordsSession.Builder().from(getConfig()).withClientLoginToken(authToken).build();
    } catch (ValidationException e) {
      throw new IllegalStateException("Could not build a session.", e);
    }
  }

  /**
   * Gets a memoized AdWords services object.
   * @throws ValidationException if AdWordsServices cannot be constructed
   */
  public synchronized AdWordsServices getAdWordsServices() throws ValidationException {
    if (adWordsServices == null) {
      adWordsServices = new AdWordsServices();
    }
    return adWordsServices;
  }

  /**
   * Gets the ID of a campaign to test with, which will be created if it doesn't
   * exist already.
   */
  public Long getTestCampaignId() {
    if (testCampaignId == null) {
      // Get the BudgetService.
      BudgetServiceInterface budgetService =
          adWordsServices.get(getSession(), BudgetServiceInterface.class);

      // Create a budget, which can be shared by multiple campaigns.
      Budget sharedBudget = new Budget();
      sharedBudget.setName("Interplanetary Cruise #" + System.currentTimeMillis());
      Money budgetAmount = new Money();
      budgetAmount.setMicroAmount(50000000L);
      sharedBudget.setAmount(budgetAmount);
      sharedBudget.setDeliveryMethod(BudgetBudgetDeliveryMethod.STANDARD);
      sharedBudget.setPeriod(BudgetBudgetPeriod.DAILY);

      BudgetOperation budgetOperation = new BudgetOperation();
      budgetOperation.setOperand(sharedBudget);
      budgetOperation.setOperator(Operator.ADD);

      // Add the budget
      Long budgetId = null;
      try {
        budgetId =
            budgetService.mutate(new BudgetOperation[] {budgetOperation}).getValue(0).getBudgetId();
      } catch (ApiException e1) {
        throw new IllegalStateException("Could not create a test budget.", e1);
      } catch (RemoteException e1) {
        throw new IllegalStateException("Could not create a test budget.", e1);
      }

      CampaignServiceInterface campaignService =
          adWordsServices.get(getSession(), CampaignServiceInterface.class);

      Campaign campaign = new Campaign();
      campaign.setName("Test Campaign #" + System.currentTimeMillis());
      campaign.setStatus(CampaignStatus.PAUSED);
      campaign.setBiddingStrategy(new ManualCPC());

      Budget budget = new Budget();
      budget.setBudgetId(budgetId);
      campaign.setBudget(budget);

      KeywordMatchSetting keywordMatch = new KeywordMatchSetting();
      keywordMatch.setOptIn(Boolean.FALSE);
      campaign.setSettings(new Setting[] {keywordMatch});

      CampaignOperation operation = new CampaignOperation();
      operation.setOperand(campaign);
      operation.setOperator(Operator.ADD);

      CampaignOperation[] operations = new CampaignOperation[] {operation};

      CampaignReturnValue result;
      try {
        result = campaignService.mutate(operations);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not create a test campaign.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not create a test campaign.", e);
      }
      if (result.getValue() != null && result.getValue().length > 0) {
        testCampaignId = result.getValue()[0].getId();
      } else {
        throw new IllegalStateException(
            "Could not create a test campaign, no campaign in response.");
      }
    }
    return testCampaignId;
  }

  /**
   * Gets the ID of an adGroup to test with, which will be created if it doesn't
   * exist already.
   */
  public Long getTestAdGroupId() {
    if (testAdGroupId == null) {
      AdGroupServiceInterface adGroupService =
          adWordsServices.get(getSession(), AdGroupServiceInterface.class);

      AdGroup adGroup = new AdGroup();
      adGroup.setName("Earth to Mars Cruises #" + System.currentTimeMillis());
      adGroup.setStatus(AdGroupStatus.ENABLED);
      adGroup.setCampaignId(getTestCampaignId());

      ManualCPCAdGroupBids adGroupBids = new ManualCPCAdGroupBids();
      adGroupBids.setKeywordMaxCpc(new Bid(new Money(null, 10000000L)));
      adGroup.setBids(adGroupBids);

      AdGroupOperation operation = new AdGroupOperation();
      operation.setOperand(adGroup);
      operation.setOperator(Operator.ADD);

      AdGroupOperation[] operations = new AdGroupOperation[] {operation};

      AdGroupReturnValue result;
      try {
        result = adGroupService.mutate(operations);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not create a test ad group.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not create a test ad group.", e);
      }
      if (result.getValue() != null && result.getValue().length > 0) {
        testAdGroupId = result.getValue()[0].getId();
      } else {
        throw new IllegalStateException(
            "Could not create a test ad group, no ad group in response.");
      }
    }
    return testAdGroupId;
  }

  /**
   * Get the ID of a text ad to test with, which will be created if it doesn't
   * exist already.
   */
  public Long getTestTextAdId() {
    if (testTextAdId == null) {
      AdGroupAdServiceInterface adGroupAdService =
          adWordsServices.get(getSession(), AdGroupAdServiceInterface.class);

      TextAd textAd1 = new TextAd();
      textAd1.setHeadline("Luxury Cruise to Mars");
      textAd1.setDescription1("Visit the Red Planet in style.");
      textAd1.setDescription2("Low-gravity fun for everyone!");
      textAd1.setDisplayUrl("www.example.com");
      textAd1.setUrl("http://www.example.com");


      AdGroupAd textAdGroupAd1 = new AdGroupAd();
      textAdGroupAd1.setAdGroupId(getTestAdGroupId());
      textAdGroupAd1.setAd(textAd1);

      AdGroupAdOperation textAdGroupAdOperation1 = new AdGroupAdOperation();
      textAdGroupAdOperation1.setOperand(textAdGroupAd1);
      textAdGroupAdOperation1.setOperator(Operator.ADD);

      AdGroupAdOperation[] operations = new AdGroupAdOperation[] {textAdGroupAdOperation1};

      AdGroupAdReturnValue result;
      try {
        result = adGroupAdService.mutate(operations);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not create a test text ad.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not create a test text ad.", e);
      }

      if (result.getValue() != null && result.getValue().length > 0) {
        testTextAdId = result.getValue()[0].getAd().getId();
      } else {
        throw new IllegalStateException("Could not create a test text ad, no ad in response.");
      }
    }
    return testTextAdId;
  }

  /**
   * Get the ID of a keyword to test with, which will be created if it doesn't
   * exist already.
   */
  public Long getTestKeywordId() {
    if (testKeywordId == null) {
      AdGroupCriterionServiceInterface adGroupCriterionService =
          adWordsServices.get(getSession(), AdGroupCriterionServiceInterface.class);
      Keyword keyword1 = new Keyword();
      keyword1.setText("mars");
      keyword1.setMatchType(KeywordMatchType.BROAD);

      BiddableAdGroupCriterion keywordBiddableAdGroupCriterion1 = new BiddableAdGroupCriterion();
      keywordBiddableAdGroupCriterion1.setAdGroupId(getTestAdGroupId());
      keywordBiddableAdGroupCriterion1.setCriterion(keyword1);

      AdGroupCriterionOperation keywordAdGroupCriterionOperation1 = new AdGroupCriterionOperation();
      keywordAdGroupCriterionOperation1.setOperand(keywordBiddableAdGroupCriterion1);
      keywordAdGroupCriterionOperation1.setOperator(Operator.ADD);

      AdGroupCriterionOperation[] operations =
          new AdGroupCriterionOperation[] {keywordAdGroupCriterionOperation1};

      AdGroupCriterionReturnValue result;
      try {
        result = adGroupCriterionService.mutate(operations);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not create a test keyword.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not create a test keyword.", e);
      }

      if (result.getValue() != null && result.getValue().length > 0) {
        testKeywordId = result.getValue()[0].getCriterion().getId();
      } else {
        throw new IllegalStateException("Could not create a test keyword, no keyword in response.");
      }
    }
    return testKeywordId;
  }

  /**
   * Gets the ID of a location extension to test with, which will be created if
   * it doesn't exist already.
   */
  public Long getTestLocationExtensionId() {
    if (testLocationExtensionId == null) {
      CampaignAdExtensionServiceInterface campaignAdExtensionService =
          adWordsServices.get(getSession(), CampaignAdExtensionServiceInterface.class);

      GeoLocationServiceInterface geoLocationService =
          adWordsServices.get(getSession(), GeoLocationServiceInterface.class);

      Address address = new Address();
      address.setStreetAddress("1600 Amphitheatre Parkway");
      address.setCityName("Mountain View");
      address.setProvinceCode("US-CA");
      address.setPostalCode("94043");
      address.setCountryCode("US");

      GeoLocationSelector selector = new GeoLocationSelector();
      selector.setAddresses(new Address[] {address});

      GeoLocation[] geoLocationResult;
      try {
        geoLocationResult = geoLocationService.get(selector);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not obtain a GeoLocation.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not obtain a GeoLocation.", e);
      }
      GeoLocation geoLocation = geoLocationResult[0];

      LocationExtension locationExtension = new LocationExtension();
      locationExtension.setAddress(geoLocation.getAddress());
      locationExtension.setGeoPoint(geoLocation.getGeoPoint());
      locationExtension.setEncodedLocation(geoLocation.getEncodedLocation());
      locationExtension.setCompanyName("Google");
      locationExtension.setPhoneNumber("650-253-0000");
      locationExtension.setSource(LocationExtensionSource.ADWORDS_FRONTEND);

      CampaignAdExtension campaignAdExtension = new CampaignAdExtension();
      campaignAdExtension.setCampaignId(getTestCampaignId());
      campaignAdExtension.setAdExtension(locationExtension);

      CampaignAdExtensionOperation operation = new CampaignAdExtensionOperation();
      operation.setOperand(campaignAdExtension);
      operation.setOperator(Operator.ADD);

      CampaignAdExtensionOperation[] operations = new CampaignAdExtensionOperation[] {operation};

      CampaignAdExtensionReturnValue result;
      try {
        result = campaignAdExtensionService.mutate(operations);
      } catch (ApiException e) {
        throw new IllegalStateException("Could not add a LocationExtension.", e);
      } catch (RemoteException e) {
        throw new IllegalStateException("Could not add a LocationExtension.", e);
      }
      if (result.getValue() != null && result.getValue().length > 0) {
        testLocationExtensionId = result.getValue()[0].getAdExtension().getId();
      } else {
        throw new IllegalStateException(
            "Could not add a LocationExtension, no extension in response.");
      }
    }
    return testLocationExtensionId;
  }

  /**
   * Gets the ID of the experiment currently associated with the specified
   * campaignId.
   *
   * @param campaignId ID of the campaign to lookup the experiment for.
   */
  public Long getExperimentIdForCampaign(Long campaignId) throws ApiException, RemoteException {
    ExperimentServiceInterface experimentService =
        adWordsServices.get(getSession(), ExperimentServiceInterface.class);
    Selector selector = new Selector();
    selector.setFields(new String[] {"Id"});
    selector
        .setPredicates(new Predicate[] {
            new Predicate("CampaignId", PredicateOperator.EQUALS, new String[] {campaignId
                .toString()}),
            new Predicate("Status", PredicateOperator.EQUALS, new String[] {"ACTIVE"})});
    ExperimentPage page = experimentService.get(selector);
    return page.getEntries()[0].getId();
  }
}
